﻿using LogSystem;
using ResourceManagerV2.Runtime.ResourceUpdate;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
using Yoozoo.Framework.Core;
using Yoozoo.Gameplay.Managers.ResourceManagerV2.Editor;
using Yoozoo.Managers.ResourceManagerV2.Framework.Resource;
using Yoozoo.Managers.ResourceManagerV2.Runtime;
using ResourceDownloadInfo = Yoozoo.Managers.ResourceManagerV2.Framework.Resource.DownloadInfo;
using Utility = Yoozoo.Managers.ResourceManagerV2.Framework.Utility;

namespace Yoozoo.Modules.Download
{
    /// <summary>
    /// 预下载
    /// </summary>
    public class PreDownloadManager : SingletonMono<PreDownloadManager>
    {
        /// <summary>
        /// 下载任务
        /// </summary>
        public class Task
        {
            public ResourceDownloadInfo ResourceDownloadInfo;
            public DownloadInfo DownloadInfo;
            public int RetryCount;
        }

        /// <summary>
        /// 总的下载状态
        /// </summary>
        public enum Status
        {
            /// <summary>
            /// 未定义，如不是边玩边下模式，或者已下载整包
            /// </summary>
            None,

            /// <summary>
            /// 读取版本文件
            /// </summary>
            Initializing,

            /// <summary>
            /// 读取版本文件失败
            /// </summary>
            InitializeFailed,

            /// <summary>
            /// 已初始化
            /// </summary>
            Initialized,

            /// <summary>
            /// 暂停状态
            /// </summary>
            Pause,

            /// <summary>
            /// 下载中
            /// </summary>
            Downloading,

            /// <summary>
            /// 下载完成
            /// </summary>
            Finish,

            /// <summary>
            /// 下载失败
            /// </summary>
            Failed,
        }

        /// <summary>
        /// 错误原因，从个性到共性排列，优先显示共性错误原因
        /// </summary>
        public enum Error
        {
            None,

            Unknown,

            /// <summary>
            /// 文件不存在
            /// </summary>
            NotFound,

            /// <summary>
            /// 网络异常
            /// </summary>
            NetworkNotReachable,
        }

        private const string RemoteVersionListFileName = "GameFrameworkVersion.dat";
        private const string RemoteVersionListInLocalFileName = "RemoteVersion.dat";
        private const string DefaultExtension = "dat";

        public bool TestPreDownloadAll;

        private Action initializeCallback;

        /// <summary>
        /// 是否确认已下载完成，下载完成后需要弹窗让玩家确认
        /// </summary>
        public bool IsChecked;

        /// <summary>
        /// 错误号
        /// </summary>
        public int ErrorId;

        /// <summary>
        /// 下载状态
        /// </summary>
        private Status downloadStatus;

        /// <summary>
        /// 下载状态
        /// </summary>
        public Status DownloadStatus
        {
            get
            {
                // 由于要重试3次才会判定为失败，但是DownloadManager在NotReachable时不会再发送WebRequest，凑不齐3次失败
                // 所以检查到NotReachable，直接返回失败
                if (downloadStatus == Status.Downloading &&
                    Application.internetReachability == NetworkReachability.NotReachable)
                    return Status.Failed;

                return downloadStatus;
            }
        }

        /// <summary>
        /// 单个文件的重试次数
        /// </summary>
        public int MaxRetryCount = 3;

        /// <summary>
        /// 重试等待时间（秒）
        /// </summary>
        private float[] retryTimes = new[] { 5f, 15f, 30f };

        /// <summary>
        /// 重试次数
        /// </summary>
        private int retryCount = 0;

        private float retryTimer = 0;

        public static PreDownloadManager Instance => GetInstance("Pre-Download");

        /// <summary>
        /// 待下载任务
        /// </summary>
        private List<Task> tasks = new List<Task>();

        /// <summary>
        /// 下载结束的任务，待检查
        /// </summary>
        private List<Task> finishTasks = new List<Task>();

        /// <summary>
        /// 校验成功的任务
        /// </summary>
        private List<Task> successTasks = new List<Task>();

        /// <summary>
        /// 失败的任务（下载失败，校验失败等）
        /// </summary>
        private List<Task> failedTasks = new List<Task>();

        /// <summary>
        /// 总大小，字节数
        /// </summary>
        private int totalSize;

        /// <summary>
        /// 已下载大小，字节数，必须校验通过才计入已下载
        /// </summary>
        private int downloadedSize;

        /// <summary>
        /// 用0~1的数表示下载的进度
        /// </summary>
        /// <returns></returns>
        public float GetProgress()
        {
            if (totalSize == 0)
                return 1;

            return (float)downloadedSize / totalSize;
        }

        /// <summary>
        /// 总大小，字节数
        /// </summary>
        /// <returns></returns>
        public int GetTotalSize()
        {
            return totalSize;
        }

        /// <summary>
        /// 已下载大小，字节数，必须校验通过才计入已下载
        /// </summary>
        /// <returns></returns>
        public int GetDownloadedSize()
        {
            return downloadedSize;
        }

        public void Initialize(int versionNumber, Action callback)
        {
            if (ResourceV2Entry.Base.EditorResourceMode)
                return;

            base.Initialize();

            if (!DownloadManager.p_IsInitialize)
            {
                var downloadManager = DownloadManager.Instance;
                var grade = DevicesGrading.Instance.DeviceGradeInfo;
                downloadManager.SetLimitByDeviceGrade(grade);
                downloadManager.Initialize();
            }

            downloadStatus = Status.Initializing;
            IsChecked = false;
            initializeCallback = callback;

            StartCoroutine(DownloadVersionFile(versionNumber));
        }

        public void Clear()
        {
            if (DownloadStatus == Status.Finish)
            {
                tasks.Clear();
                finishTasks.Clear();
                failedTasks.Clear();
                successTasks.Clear();

                // 销毁DownloadManager
                if (DownloadManager.p_IsInitialize && DownloadManager.Instance.IsIdle())
                    DownloadManager.Instance.Dispose();
            }
        }

        private IEnumerator DownloadVersionFile(int versionNumber)
        {
            string versionInfoUrl = string.Format($"{ResourceV2Entry.Resource.UpdatePrefixUri}versionInfo_{versionNumber}.json?t={DateTime.UtcNow.Ticks}");
            UnityWebRequest unityWebRequest = UnityWebRequest.Get(versionInfoUrl);
            yield return unityWebRequest.SendWebRequest();
            bool isError = unityWebRequest.result != UnityWebRequest.Result.Success;
            string json = unityWebRequest.downloadHandler.text;
            unityWebRequest.Dispose();
            if (isError)
            {
                downloadStatus = Status.InitializeFailed;
                initializeCallback?.Invoke();
                yield break;
            }
            
            var versionInfo = (VersionInfo)JsonUtility.FromJson<VersionInfoSeriazable>(json);
            int versionListLength = versionInfo.VersionListLength;
            int versionListHashCode = versionInfo.VersionListHashCode;
            int versionListZipLength = versionInfo.VersionListZipLength;
            int versionListZipHashCode = versionInfo.VersionListZipHashCode;

            int dotPosition = RemoteVersionListFileName.LastIndexOf('.');
            var fileName = RemoteVersionListFileName.Substring(0, dotPosition);
            var extension = RemoteVersionListFileName.Substring(dotPosition + 1);
            string versionNameWithCrc32 = Utility.Text.Format("{0}.{2:x8}.{1}", fileName, extension, versionListHashCode);
            string versionUrl = Utility.Path.GetRemotePath(Path.Combine(ResourceV2Entry.Resource.UpdatePrefixUri, versionNameWithCrc32));

            unityWebRequest = UnityWebRequest.Get(versionUrl);
            yield return unityWebRequest.SendWebRequest();
            isError = unityWebRequest.result != UnityWebRequest.Result.Success;
            var bytes = unityWebRequest.downloadHandler.data;
            unityWebRequest.Dispose();

            if (isError)
            {
                downloadStatus = Status.InitializeFailed;
                initializeCallback?.Invoke();
                yield break;
            }

            if (bytes == null || bytes.Length != versionListZipLength)
            {
                downloadStatus = Status.InitializeFailed;
                initializeCallback?.Invoke();
                yield break;
            }

            int hashCode = Utility.Verifier.GetCrc32(bytes);
            if (hashCode != versionListZipHashCode)
            {
                downloadStatus = Status.InitializeFailed;
                initializeCallback?.Invoke();
                yield break;
            }

            if (versionListLength != versionListZipLength && versionListHashCode != versionListZipHashCode)
            {
                // 解压
                bytes = Utility.ZipUtil.Decompress(bytes);
                if (bytes == null || bytes.Length != versionListLength)
                {
                    downloadStatus = Status.InitializeFailed;
                    initializeCallback?.Invoke();
                    yield break;
                }
                hashCode = Utility.Verifier.GetCrc32(bytes);
                if (hashCode != versionListHashCode)
                {
                    downloadStatus = Status.InitializeFailed;
                    initializeCallback?.Invoke();
                    yield break;
                }
            }
            
            var updatableVersionListSerializer = ResourceV2Entry.Resource.UpdatableVersionListSerializer;
            var nextVersionList = updatableVersionListSerializer.Deserialize(new MemoryStream(bytes, false));

            string path = Utility.Path.GetRegularPath(Path.Combine(ResourceV2Entry.Resource.ReadWritePath, RemoteVersionListFileName));
            UpdatableVersionList currentVersionList;
            if (File.Exists(path))
            {
                currentVersionList = updatableVersionListSerializer.Deserialize(new FileStream(path, FileMode.Open, FileAccess.Read));
            }
            else
            {
                string remotePath = Utility.Path.GetRemotePath(Path.Combine(UMTResource.Resource.ReadOnlyPath, RemoteVersionListInLocalFileName));

                unityWebRequest = UnityWebRequest.Get(remotePath);
                yield return unityWebRequest.SendWebRequest();
                isError = unityWebRequest.result != UnityWebRequest.Result.Success;
                var versionListData = unityWebRequest.downloadHandler.data;
                unityWebRequest.Dispose();

                if (isError)
                {
                    downloadStatus = Status.InitializeFailed;
                    initializeCallback?.Invoke();
                    yield break;
                }
                currentVersionList = updatableVersionListSerializer.Deserialize(new MemoryStream(versionListData, false));
            }
            
            var nextResources = nextVersionList.GetResources();
            var currentResources = currentVersionList.GetResources();
            var currentResourceMap = new Dictionary<string, UpdatableVersionList.Resource>();
            foreach (var currentResource in currentResources)
            {
                if (!TestPreDownloadAll)
                {
                    currentResourceMap.Add(currentResource.RawName, currentResource);
                }
            }

            tasks.Clear();
            finishTasks.Clear();
            failedTasks.Clear();
            successTasks.Clear();

            foreach (var nextResource in nextResources)
            {
                bool needUpdate = false;
                if (currentResourceMap.TryGetValue(nextResource.RawName, out var currentResource))
                {
                    if (currentResource.Length != nextResource.Length || currentResource.HashCode != nextResource.HashCode)
                    {
                        // 资源更新
                        needUpdate = true;
                    }
                }
                else
                {
                    // 新增资源
                    needUpdate = true;
                }

                if (needUpdate)
                {
                    totalSize += nextResource.ZipLength;

                    string localPath = $"pd/{nextResource.ZipHashCode:x8}.{DefaultExtension}";
                    string localFullPath = Path.Combine(ResourceV2Entry.Resource.ReadWritePath, localPath);
                    if (File.Exists(localFullPath))
                    {
                        // 校验之后才会复制到正确位置，只要文件存在就认为已下载
                        downloadedSize += nextResource.ZipLength;
                        continue;
                    }

                    // 添加下载任务
                    string fullNameWithCrc32 = Utility.Text.Format("{0}.{1:x8}.{2}", nextResource.RawName, nextResource.HashCode, DefaultExtension);
                    var downloadInfo = new ResourceDownloadInfo
                    {
                        Url = Path.Combine(ResourceV2Entry.Resource.UpdatePrefixUri, fullNameWithCrc32),
                        ZipHashCode = nextResource.ZipHashCode,
                        ZipLength = nextResource.ZipLength,
                        HashCode = nextResource.HashCode,
                        Length = nextResource.Length,
                        Path = Utility.Path.GetRegularPath(localFullPath)
                    };
                    var task = new Task()
                    {
                        ResourceDownloadInfo = downloadInfo
                    };
                    tasks.Add(task);
                }
            }

            downloadStatus = Status.Initialized;
            
            // 可能已全部下载完毕，此时状态应该是Finish
            UpdateStatus();
            initializeCallback?.Invoke();
        }

        /// <summary>
        /// 有文件下载结束（成功或者失败），更新下载状态
        /// </summary>
        private void UpdateStatus()
        {
            // 任意下载任务失败，状态改为失败。但是其他下载还在继续
            if (failedTasks.Count > 0)
            {
                downloadStatus = Status.Failed;
                return;
            }

            // 无待下载，待校验任务，结束
            if (tasks.Count == 0 && finishTasks.Count == 0)
            {
                downloadStatus = Status.Finish;
            }
        }

        /// <summary>
        /// 玩家点击重试，重置自动重试次数
        /// </summary>
        public void ManuelRetry()
        {
            if (downloadStatus != Status.Failed)
                return;

            retryTimer = 0;
            retryCount = 0;
            RetryAll();
        }

        /// <summary>
        /// 全部重试
        /// </summary>
        private void RetryAll()
        {
            if (downloadStatus != Status.Failed)
                return;

            downloadStatus = Status.Downloading;
            ErrorId = 0;

            // 重置失败任务的重试次数，重新下载
            foreach (var failedTask in failedTasks)
            {
                failedTask.RetryCount = 0;
                tasks.Add(failedTask);
                StartOne(failedTask);
            }
            failedTasks.Clear();
        }

        /// <summary>
        /// 重试单个下载任务
        /// </summary>
        /// <param name="task"></param>
        private void Retry(Task task)
        {
            var resourceDownloadInfo = task.ResourceDownloadInfo;
            if (task.RetryCount >= MaxRetryCount)
            {
                // 重试次数达到上限，移动到失败列表中，更新状态
                UnityEngine.Debug.LogErrorFormat("Retry failed: {0} -> {1}, Retry: {2}", resourceDownloadInfo.Url, resourceDownloadInfo.Path, task.RetryCount);
                failedTasks.Add(task);
                tasks.Remove(task);

                UpdateErrorId(task.DownloadInfo.ErrorMessage);
                UpdateStatus();
                return;
            }

            // 重新下载
            task.RetryCount++;
            //UnityEngine.Debug.LogErrorFormat("Retry: {0} -> {1}, Retry: {2}", resourceDownloadInfo.Url, resourceDownloadInfo.Path, task.RetryCount);
            task.DownloadInfo = StartOne(task);
        }

        /// <summary>
        /// 检查失败原因
        /// </summary>
        /// <param name="errorMessage"></param>
        private void UpdateErrorId(string errorMessage)
        {
            int errorId = 0;

            if (string.IsNullOrEmpty(errorMessage))
            {
                errorId = (int)Error.Unknown;
            }
            else
            {
                // Failed to receive data
                // Cannot connect to destination host

                if (errorMessage.Contains("Not Found"))
                {
                    errorId = (int)Error.NotFound;
                }
            }

            // 各个文件有各个文件的下载失败的原因，通用的原因优先级调高，如网络问题，空间不足，权限不足等等
            // 个别问题优先级降低，如文件不存在，校验出错等
            if (errorId > ErrorId)
            {
                ErrorId = errorId;
            }
        }

        public void Pause()
        {
            if (downloadStatus != Status.Downloading)
                return;

            if (!DownloadManager.p_IsInitialize)
                Debug.LogErrorFormat(LogModule._Default_, "DownloadManager is not initialized!");

            downloadStatus = Status.Pause;
            DownloadManager.Instance.Pause();
        }

        public void Resume()
        {
            if (downloadStatus != Status.Pause)
                return;

            if (!DownloadManager.p_IsInitialize)
                Debug.LogErrorFormat(LogModule._Default_, "DownloadManager is not initialized!");

            downloadStatus = Status.Downloading;
            DownloadManager.Instance.Resume();
        }

        /// <summary>
        /// 开始下载
        /// </summary>
        public void StartAll()
        {
            if (downloadStatus != Status.Initialized)
                return;

            downloadStatus = Status.Downloading;
            foreach (var task in tasks)
            {
                task.DownloadInfo = StartOne(task);
            }
        }

        /// <summary>
        /// 开始单个文件的下载
        /// </summary>
        /// <param name="task"></param>
        /// <returns></returns>
        private DownloadInfo StartOne(Task task)
        {
            if (!DownloadManager.p_IsInitialize)
                Debug.LogErrorFormat(LogModule._Default_, "DownloadManager is not initialized!");

            var resourceDownloadInfo = task.ResourceDownloadInfo;
            int priority = resourceDownloadInfo.ZipHashCode;

            var url = resourceDownloadInfo.Url;
            // 本地下载路径添加download后缀，检查成功后再改名
            var path = resourceDownloadInfo.Path + ".pd";
            var length = (ulong)resourceDownloadInfo.ZipLength;

            return DownloadManager.Instance.StartDownload(url, path, length, priority,
                (isSuccess) =>
                {
                    OnDownloadFinish(isSuccess, task);
                });
        }

        private void OnDownloadFinish(bool isSuccess, object userData)
        {
            var task = userData as Task;
            if (task == null)
                return;

            var resourceDownloadInfo = task.ResourceDownloadInfo;
            // 远端资源文件的Url
            string url = resourceDownloadInfo.Url;
            // 应该放置的本地资源文件路径
            string path = resourceDownloadInfo.Path;

            //UnityEngine.Debug.LogErrorFormat("Download: {0} -> {1}, {2}", downloadInfo.Url, downloadInfo.Path, isSuccess);
            if (isSuccess)
            {
                if (downloadStatus == Status.Pause)
                {
                    // 暂停的情况，下载完成的内容丢弃，添加到重试列表，但是不增加重试计数
                    task.DownloadInfo = StartOne(task);
                }
                else
                {
                    finishTasks.Add(task);
                    tasks.Remove(task);
                }
            }
            else
            {
                // 这里是其他异常，可能是断网，不删除文件，等下次断点续传
                UnityEngine.Debug.LogErrorFormat("Download failed: {0} -> {1}, {2}", url, path, task.DownloadInfo.ErrorMessage);
                Retry(task);
            }
        }

        private void Update()
        {
            AutoRetry();

            TryCheckResource();
        }

        /// <summary>
        /// 自动重试
        /// </summary>
        private void AutoRetry()
        {
            // 如果下载失败，自动等待5，15，30秒自动进行重试
            if (DownloadStatus != Status.Failed)
                return;

            if (Application.internetReachability == NetworkReachability.NotReachable)
                return;

            if (retryTimer > 0)
            {
                retryTimer -= Time.deltaTime;
                if (retryTimer < 0)
                {
                    RetryAll();
                }
            }
            else
            {
                if (retryCount >= retryTimes.Length)
                    return;

                retryTimer = retryTimes[retryCount];
                retryCount++;
            }
        }

        private void TryCheckResource()
        {
            if (finishTasks.Count == 0)
                return;

            if (checkResourceThread.State == CheckResourceThread.StateType.Running)
                return;

            checkResourceThread.Start(CheckResource);
        }

        private CheckResourceThread checkResourceThread = new CheckResourceThread();

        public void CheckResource()
        {
            var task = finishTasks[0];
            var resourceDownloadInfo = task.ResourceDownloadInfo;
            // 远端资源文件的Url
            string url = resourceDownloadInfo.Url;
            // 应该放置的本地资源文件路径
            string targetPath = resourceDownloadInfo.Path;
            // 临时放置的下载文件路径
            string downloadPath = task.DownloadInfo.Path;

            var bytes = File.ReadAllBytes(downloadPath);
            bool isSuccess = true;
            if (bytes.Length != resourceDownloadInfo.ZipLength)
            {
                isSuccess = false;
            }
            else
            {
                int hashCode = Utility.Verifier.GetCrc32(bytes);
                if (hashCode != resourceDownloadInfo.ZipHashCode)
                {
                    isSuccess = false;
                }
            }
            
            if (isSuccess)
            {
                // 校验成功
                downloadedSize += resourceDownloadInfo.ZipLength;

                if (File.Exists(targetPath))
                    File.Delete(targetPath);
                File.Move(downloadPath, targetPath);

                successTasks.Add(task);
                finishTasks.Remove(task);
                UpdateStatus();
            }
            else
            {
                // 这里CRC校验失败，直接删除文件，重新下载
                if (File.Exists(downloadPath))
                    File.Delete(downloadPath);
                
                UnityEngine.Debug.LogErrorFormat("Download success but unzip failed: {0} -> {1}", url, targetPath);

                finishTasks.Remove(task);
                tasks.Add(task);
                Retry(task);
            }
        }

        /// <summary>
        /// 下载速度（字节/秒)
        /// </summary>
        public float GetSpeed()
        {
            if (!DownloadManager.p_IsInitialize)
                return 0;

            return DownloadManager.Instance.Speed;
        }

    }
}
